#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
产品资料共享系统 - 生产环境启动脚本
支持以守护进程(daemon)方式运行，使用 -d 参数
"""

import os
import sys
import logging
import argparse
import signal
import atexit
from logging.handlers import RotatingFileHandler

# 确保生产环境设置
os.environ["DEBUG"] = "false"
os.environ["SECRET_KEY"] = os.environ.get("SECRET_KEY", os.urandom(24).hex())

# 守护进程实现
def daemonize():
    """将当前进程转换为守护进程"""
    import datetime
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    
    # 确保日志目录存在
    try:
        os.makedirs('logs', exist_ok=True)
    except Exception as e:
        sys.stderr.write(f"创建日志目录失败: {e}\n")
        sys.exit(1)
    
    # 在fork前记录日志
    try:
        with open('logs/daemon_start.log', 'a+') as f:
            f.write(f"{os.getpid()}: 开始守护进程化... 时间: {current_time}\n")
    except Exception as e:
        sys.stderr.write(f"写入日志失败: {e}\n")
        # 继续执行，不要因为日志问题而退出
    
    # 第一次fork，脱离父进程
    try:
        pid = os.fork()
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 第一次fork, 返回值: {pid}\n")
        except Exception:
            pass  # 忽略日志错误
            
        if pid > 0:
            # 父进程等待一小段时间，确保子进程已经启动
            import time
            time.sleep(0.5)
            sys.exit(0)  # 父进程退出
    except OSError as e:
        try:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: 第一次fork失败: {e}\n")
        except Exception:
            pass  # 忽略日志错误
        sys.stderr.write(f"第一次fork失败: {e}\n")
        sys.exit(1)
    
    # 脱离终端
    try:
        os.setsid()
        os.umask(0)
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 已调用setsid和umask\n")
        except Exception:
            pass  # 忽略日志错误
    except Exception as e:
        try:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: setsid或umask失败: {e}\n")
        except Exception:
            pass  # 忽略日志错误
        sys.stderr.write(f"setsid或umask失败: {e}\n")
        sys.exit(1)
    
    # 第二次fork，防止获取终端
    try:
        pid = os.fork()
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 第二次fork, 返回值: {pid}\n")
        except Exception:
            pass  # 忽略日志错误
            
        if pid > 0:
            # 第一个子进程等待一小段时间，确保第二个子进程已经启动
            import time
            time.sleep(0.5)
            sys.exit(0)  # 第一个子进程退出
    except OSError as e:
        try:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: 第二次fork失败: {e}\n")
        except Exception:
            pass  # 忽略日志错误
        sys.stderr.write(f"第二次fork失败: {e}\n")
        sys.exit(1)
    
    # 重定向标准文件描述符
    try:
        sys.stdout.flush()
        sys.stderr.flush()
        
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 准备重定向标准文件描述符\n")
        except Exception:
            pass  # 忽略日志错误
        
        # 确保日志目录存在
        try:
            os.makedirs('logs', exist_ok=True)
        except Exception as e:
            sys.stderr.write(f"创建日志目录失败: {e}\n")
            # 继续执行，尝试其他操作
        
        # 将标准输入重定向到/dev/null
        try:
            # 在Linux/Unix系统上使用/dev/null，在Windows上使用NUL
            devnull = 'NUL' if os.name == 'nt' else '/dev/null'
            with open(devnull, 'r') as null_in:
                os.dup2(null_in.fileno(), sys.stdin.fileno())
        except Exception as e:
            try:
                with open('logs/daemon_error.log', 'a+') as f:
                    f.write(f"{os.getpid()}: 重定向标准输入失败: {e}\n")
            except Exception:
                pass  # 忽略日志错误
        
        # 标准输出和错误输出重定向到日志文件
        try:
            with open('logs/daemon.log', 'a+') as log_out:
                os.dup2(log_out.fileno(), sys.stdout.fileno())
                os.dup2(log_out.fileno(), sys.stderr.fileno())
        except Exception as e:
            try:
                with open('logs/daemon_error.log', 'a+') as f:
                    f.write(f"{os.getpid()}: 重定向标准输出和错误输出失败: {e}\n")
            except Exception:
                pass  # 忽略日志错误
        
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 已重定向标准文件描述符\n")
        except Exception:
            pass  # 忽略日志错误
    except Exception as e:
        try:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: 重定向文件描述符过程中发生错误: {e}\n")
        except Exception:
            pass  # 忽略日志错误
    
    # 创建PID文件前先检查是否已经存在
    try:
        # 如果PID文件已存在，先检查其中的进程是否还在运行
        if os.path.exists('logs/app.pid'):
            try:
                with open('logs/app.pid', 'r') as f:
                    old_pid_str = f.read().strip()
                
                if old_pid_str:
                    try:
                        old_pid = int(old_pid_str)
                        try:
                            # 检查进程是否存在
                            os.kill(old_pid, 0)
                            # 如果能执行到这里，说明进程存在
                            with open('logs/daemon_error.log', 'a+') as f:
                                f.write(f"{os.getpid()}: 已有进程在运行 (PID: {old_pid})，无法启动新实例\n")
                            sys.exit(1)
                        except OSError:
                            # 进程不存在，可以覆盖PID文件
                            with open('logs/daemon_start.log', 'a+') as f:
                                f.write(f"{os.getpid()}: 发现过期的PID文件，将被覆盖\n")
                    except ValueError:
                        # PID不是有效的整数，可以覆盖
                        with open('logs/daemon_start.log', 'a+') as f:
                            f.write(f"{os.getpid()}: PID文件包含无效的PID，将被覆盖\n")
            except Exception as e:
                with open('logs/daemon_start.log', 'a+') as f:
                    f.write(f"{os.getpid()}: 读取PID文件失败: {e}，将创建新的PID文件\n")
        
        # 创建新的PID文件
        pid = str(os.getpid())
        with open('logs/app.pid', 'w+') as f:
            f.write(pid + '\n')
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 已创建PID文件\n")
        except Exception:
            pass  # 忽略日志错误
    except Exception as e:
        try:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: 创建PID文件失败: {e}\n")
        except Exception:
            pass  # 忽略日志错误
    
    # 注册退出函数，清理PID文件
    def cleanup():
        try:
            if os.path.exists('logs/app.pid'):
                os.remove('logs/app.pid')
                try:
                    with open('logs/daemon_start.log', 'a+') as f:
                        f.write(f"已清理PID文件\n")
                except Exception:
                    pass  # 忽略日志错误
        except OSError as e:
            try:
                with open('logs/daemon_error.log', 'a+') as f:
                    f.write(f"清理PID文件失败: {e}\n")
            except Exception:
                pass  # 忽略日志错误
    
    atexit.register(cleanup)
    
    # 处理信号
    def signal_handler(signo, frame):
        if signo in (signal.SIGTERM, signal.SIGINT):
            try:
                with open('logs/daemon_start.log', 'a+') as f:
                    f.write(f"{os.getpid()}: 收到信号 {signo}，准备退出\n")
            except Exception:
                pass  # 忽略日志错误
            sys.exit(0)
    
    # 注册信号处理器
    try:
        signal.signal(signal.SIGTERM, signal_handler)
        signal.signal(signal.SIGINT, signal_handler)
        try:
            with open('logs/daemon_start.log', 'a+') as f:
                f.write(f"{os.getpid()}: 已注册信号处理器\n")
        except Exception:
            pass  # 忽略日志错误
    except Exception as e:
        try:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: 注册信号处理器失败: {e}\n")
        except Exception:
            pass  # 忽略日志错误

if __name__ == "__main__":
# 解析命令行参数
    parser = argparse.ArgumentParser(description='产品资料共享系统启动脚本')
    parser.add_argument('-d', '--daemon', action='store_true', help='以守护进程方式运行')
    parser.add_argument('-s', '--stop', action='store_true', help='停止正在运行的守护进程')
    parser.add_argument('-r', '--restart', action='store_true', help='重启应用')
    args = parser.parse_args()
    
    # 停止守护进程
    if args.stop or args.restart:
        if os.path.exists('logs/app.pid'):
            try:
                with open('logs/app.pid', 'r') as f:
                    pid_str = f.read().strip()
                
                if not pid_str:
                    print("PID文件为空，应用可能未在运行")
                    os.remove('logs/app.pid')
                else:
                    try:
                        pid = int(pid_str)
                        
                        # 检查进程是否存在
                        import platform
                        process_exists = False
                        
                        if platform.system() == 'Windows':
                            # Windows系统下检查进程
                            import subprocess
                            try:
                                output = subprocess.check_output(f'tasklist /FI "PID eq {pid}" /NH', shell=True)
                                process_exists = b'No tasks' not in output
                            except subprocess.CalledProcessError:
                                process_exists = False
                        else:
                            # Unix系统下检查进程
                            try:
                                os.kill(pid, 0)  # 发送空信号检查进程是否存在
                                process_exists = True
                            except OSError:
                                process_exists = False
                        
                        if process_exists:
                            print(f"正在停止应用 (PID: {pid})...")
                            
                            # 发送终止信号
                            try:
                                if platform.system() == 'Windows':
                                    # Windows下使用taskkill
                                    subprocess.call(f'taskkill /F /PID {pid}', shell=True)
                                else:
                                    # Unix下使用kill
                                    os.kill(pid, signal.SIGTERM)
                                
                                # 等待进程结束
                                import time
                                for i in range(10):  # 最多等待10秒
                                    time.sleep(1)
                                    
                                    # 再次检查进程是否存在
                                    still_exists = False
                                    
                                    if platform.system() == 'Windows':
                                        try:
                                            output = subprocess.check_output(f'tasklist /FI "PID eq {pid}" /NH', shell=True)
                                            still_exists = b'No tasks' not in output
                                        except subprocess.CalledProcessError:
                                            still_exists = False
                                    else:
                                        try:
                                            os.kill(pid, 0)
                                            still_exists = True
                                        except OSError:
                                            still_exists = False
                                    
                                    if not still_exists:
                                        print(f"应用已停止 (PID: {pid})")
                                        break
                                else:
                                    print(f"警告: 应用 (PID: {pid}) 未能在10秒内停止，尝试强制终止...")
                                    
                                    # 尝试强制终止
                                    try:
                                        if platform.system() == 'Windows':
                                            subprocess.call(f'taskkill /F /PID {pid}', shell=True)
                                        else:
                                            os.kill(pid, signal.SIGKILL)
                                        print("应用已强制终止")
                                    except (OSError, subprocess.CalledProcessError) as e:
                                        print(f"强制终止失败: {e}")
                            except (OSError, subprocess.CalledProcessError) as e:
                                print(f"停止应用失败: {e}")
                        else:
                            print(f"PID {pid} 不存在，应用可能已经停止")
                        
                        # 无论如何，删除PID文件
                        if os.path.exists('logs/app.pid'):
                            os.remove('logs/app.pid')
                            print("已删除PID文件")
                            
                    except ValueError:
                        print(f"PID文件包含无效的PID: {pid_str}")
                        os.remove('logs/app.pid')
                        print("已删除无效的PID文件")
            except (IOError, OSError) as e:
                print(f"无法读取PID文件: {e}")
                try:
                    os.remove('logs/app.pid')
                    print("已删除PID文件")
                except OSError as e:
                    print(f"删除PID文件失败: {e}")
        else:
            print("未找到PID文件，应用可能未在运行")
        
        # 如果只是停止，不重启，则退出
        if not args.restart:
            sys.exit(0)
    
    # 如果指定了-d参数，以守护进程方式运行
    daemon_mode = False  # 标记是否已经转为守护进程
    
    if args.daemon or args.restart:
        import datetime
        import platform
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        
        # 检查操作系统，Windows不支持fork
        if platform.system() == 'Windows':
            print("警告: Windows系统不支持守护进程模式，将以普通模式启动")
            # 创建日志目录
            os.makedirs('logs', exist_ok=True)
            with open('logs/daemon_info.log', 'a+') as f:
                f.write(f"{os.getpid()}: Windows系统不支持守护进程模式，将以普通模式启动, 时间: {current_time}\n")
        else:
            print("正在以守护进程方式启动...")
            # 在转为守护进程前记录一些信息
            os.makedirs('logs', exist_ok=True)
            with open('logs/daemon_info.log', 'a+') as f:
                f.write(f"{os.getpid()}: 准备转为守护进程模式, 时间: {current_time}\n")
            
            # 转为守护进程
            daemonize()
            daemon_mode = True  # 标记已经转为守护进程
            
            # 守护进程启动后记录信息
            current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            with open('logs/daemon_info.log', 'a+') as f:
                f.write(f"{os.getpid()}: 守护进程已启动, 时间: {current_time}\n")
        
    # 创建日志目录
    os.makedirs('logs', exist_ok=True)
    
    # 设置日志
    log_formatter = logging.Formatter('%(asctime)s [%(levelname)s] - %(message)s')
    log_handler = RotatingFileHandler('logs/app.log', maxBytes=10485760, backupCount=10)
    log_handler.setFormatter(log_formatter)
    
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    logger.addHandler(log_handler)
    
    # 添加控制台日志处理器
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(log_formatter)
    logger.addHandler(console_handler)
    
    logger.info("正在启动产品资料共享系统...")
    
    # 检查必要的目录是否存在
    os.makedirs('static/uploads', exist_ok=True)
    os.makedirs('data', exist_ok=True)
    
    # 检查必要的依赖
    try:
        import flask
        import requests
        import bs4
        from PIL import Image
    except ImportError as e:
        logger.error(f"错误: 缺少必要的依赖 - {e}")
        logger.error("请运行: pip install flask werkzeug requests beautifulsoup4 pillow")
        sys.exit(1)
    
    # 检查可选依赖
    try:
        import jieba
        logger.info("已安装jieba: 将使用高级中文分词和关键词提取")
    except ImportError:
        logger.warning("未安装jieba: 将使用基本关键词提取")
    
    try:
        import easyocr
        logger.info("已安装easyocr: 将启用图片OCR功能")
    except ImportError:
        logger.warning("未安装easyocr: 图片OCR功能将不可用")
    
    try:
        # 导入并运行Flask应用
        from app import app, check_database_status
        # 环境配置
        from config import HOST, PORT
        
        # 数据库检查
        if not check_database_status():
            logger.error("数据库检查失败，应用无法启动")
            sys.exit(1)
    except Exception as e:
        logger.error(f"导入应用模块失败: {e}")
        sys.exit(1)
            
    # 检查是否已经有实例在运行
    # 在守护进程模式下，我们已经创建了PID文件，所以需要特殊处理
    if os.path.exists('logs/app.pid') and not daemon_mode:
        try:
            with open('logs/app.pid', 'r') as f:
                old_pid_str = f.read().strip()
                
            if old_pid_str:  # 确保PID文件不是空的
                try:
                    old_pid = int(old_pid_str)
                    
                    # 检查进程是否存在
                    import platform
                    if platform.system() == 'Windows':
                        # Windows系统下检查进程
                        import subprocess
                        try:
                            subprocess.check_output(f'tasklist /FI "PID eq {old_pid}" /NH', shell=True)
                            if b'No tasks' in subprocess.check_output(f'tasklist /FI "PID eq {old_pid}" /NH', shell=True):
                                # 进程不存在
                                logger.warning(f"发现过期的PID文件 (PID: {old_pid} 不存在)，将被覆盖")
                                os.remove('logs/app.pid')
                            else:
                                # 进程存在，检查是否是Python进程
                                output = subprocess.check_output(f'tasklist /FI "PID eq {old_pid}" /FO CSV /NH', shell=True).decode('utf-8')
                                if 'python' in output.lower():
                                    logger.error(f"应用已经在运行中 (PID: {old_pid})，如需重启请先停止现有实例")
                                    sys.exit(1)
                                else:
                                    # PID存在但不是Python进程，可能是PID被重用
                                    logger.warning(f"PID {old_pid} 存在但不是Python进程，将覆盖PID文件")
                                    os.remove('logs/app.pid')
                        except subprocess.CalledProcessError:
                            # 命令执行失败，假设进程不存在
                            logger.warning(f"检查PID {old_pid} 失败，将覆盖PID文件")
                            os.remove('logs/app.pid')
                    else:
                        # Unix系统下检查进程
                        try:
                            os.kill(old_pid, 0)  # 发送空信号检查进程是否存在
                            
                            # 进程存在，但还需要确认是否是Python进程
                            try:
                                # 在Unix系统上，可以通过/proc/{pid}/cmdline检查命令行
                                if os.path.exists(f'/proc/{old_pid}/cmdline'):
                                    with open(f'/proc/{old_pid}/cmdline', 'r') as cmd_file:
                                        cmdline = cmd_file.read()
                                        if 'python' in cmdline or 'gunicorn' in cmdline:
                                            logger.error(f"应用已经在运行中 (PID: {old_pid})，如需重启请先停止现有实例")
                                            sys.exit(1)
                                        else:
                                            # PID存在但不是Python进程，可能是PID被重用
                                            logger.warning(f"PID {old_pid} 存在但不是Python进程，将覆盖PID文件")
                                            os.remove('logs/app.pid')
                                else:
                                    # 无法确认进程类型，保守处理
                                    logger.error(f"应用可能在运行中 (PID: {old_pid})，如需重启请先停止现有实例")
                                    sys.exit(1)
                            except (IOError, OSError):
                                # 无法读取cmdline，保守处理
                                logger.error(f"应用可能在运行中 (PID: {old_pid})，如需重启请先停止现有实例")
                                sys.exit(1)
                                
                        except OSError:  # 进程不存在
                            logger.warning(f"发现过期的PID文件 (PID: {old_pid} 不存在)，将被覆盖")
                            os.remove('logs/app.pid')
                except ValueError:
                    # PID不是有效的整数
                    logger.warning(f"PID文件包含无效的PID: {old_pid_str}，将被覆盖")
                    os.remove('logs/app.pid')
            else:
                # PID文件为空
                logger.warning("PID文件为空，将被覆盖")
                os.remove('logs/app.pid')
        except (IOError, OSError) as e:
            # 无法读取PID文件
            logger.warning(f"无法读取PID文件: {e}，将被覆盖")
            try:
                os.remove('logs/app.pid')
            except OSError:
                pass
    
    # 在非守护进程模式下，或者在Windows系统上，需要在这里创建PID文件
    # 在守护进程模式下，daemonize()函数已经创建了PID文件，不需要再次创建
    if not daemon_mode:
        if not os.path.exists('logs/app.pid'):
            try:
                with open('logs/app.pid', 'w') as f:
                    f.write(str(os.getpid()))
                logger.info(f"已创建PID文件: {os.getpid()}")
            except Exception as e:
                logger.error(f"创建PID文件失败: {e}")
        else:
            # 如果PID文件已存在，确保它包含当前进程的PID
            try:
                with open('logs/app.pid', 'r') as f:
                    file_pid = f.read().strip()
                
                if file_pid != str(os.getpid()):
                    logger.warning(f"PID文件包含错误的PID: {file_pid}，当前PID: {os.getpid()}，将更新")
                    with open('logs/app.pid', 'w') as f:
                        f.write(str(os.getpid()))
            except Exception as e:
                logger.error(f"检查或更新PID文件失败: {e}")
    
    # 使用gunicorn启动应用（如果可用）
    try:
        # 在守护进程模式下，记录额外的日志信息
        if args.daemon:
            with open('logs/daemon_info.log', 'a+') as f:
                f.write(f"{os.getpid()}: 准备导入gunicorn并启动应用\n")
        
        import gunicorn
        logger.info("使用gunicorn启动应用...")
        
        # 使用gunicorn的Python API启动应用
        from gunicorn.app.base import BaseApplication
        
        class StandaloneApplication(BaseApplication):
            def __init__(self, app, options=None):
                self.options = options or {}
                self.application = app
                super().__init__()
            
            def load_config(self):
                for key, value in self.options.items():
                    if key in self.cfg.settings and value is not None:
                        self.cfg.set(key.lower(), value)
            
            def load(self):
                return self.application
        
        # gunicorn配置
        options = {
            'bind': f"{HOST}:{PORT}",
            'workers': 4,  # 通常设置为 CPU核心数 * 2 + 1
            'worker_class': 'sync',
            'timeout': 120,
            'accesslog': 'logs/access.log',
            'errorlog': 'logs/error.log',
            'loglevel': 'info',
            'capture_output': True,
            # 如果我们已经使用了自己的守护进程实现，就不要让gunicorn再次守护进程化
            'daemon': False  # 我们已经在上面实现了守护进程
        }
        
        logger.info(f"应用将在 {HOST}:{PORT} 上运行")
        if args.daemon:
            logger.info("应用将以守护进程方式运行，日志将写入logs目录")
            with open('logs/daemon_info.log', 'a+') as f:
                f.write(f"{os.getpid()}: 准备启动gunicorn应用，配置: {options}\n")
        
        StandaloneApplication(app, options).run()
        
    except ImportError:
        # 如果没有gunicorn，回退到Flask内置服务器
        logger.warning("未安装gunicorn，将使用Flask内置服务器（不推荐用于生产环境）")
        logger.warning("建议安装gunicorn: pip install gunicorn")
        logger.info(f"应用将在 {HOST}:{PORT} 上运行")
        
        # 在守护进程模式下，记录额外的日志信息
        if args.daemon:
            with open('logs/daemon_info.log', 'a+') as f:
                f.write(f"{os.getpid()}: 未找到gunicorn，将使用Flask内置服务器启动\n")
        
        try:
            app.run(debug=False, host=HOST, port=PORT)
        except Exception as e:
            error_msg = f"Flask服务器启动失败: {str(e)}"
            logger.exception(error_msg)
            if args.daemon:
                with open('logs/daemon_error.log', 'a+') as f:
                    f.write(f"{os.getpid()}: {error_msg}\n")
            sys.exit(1)
            
    except Exception as e:
        error_msg = f"启动应用时发生错误: {str(e)}"
        logger.exception(error_msg)
        if args.daemon:
            with open('logs/daemon_error.log', 'a+') as f:
                f.write(f"{os.getpid()}: {error_msg}\n")
        sys.exit(1)
